package com.company.module.net.interceptor.changeRequest;

import android.text.TextUtils;

import com.company.module.net.BuildConfig;
import com.company.module.net.interceptor.changeRequest.cache.Cache;
import com.company.module.net.interceptor.changeRequest.cache.LruCache;
import com.company.module.net.interceptor.InvalidUrlException;
import com.company.module.net.utils.LogUtil;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import okhttp3.HttpUrl;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;

/**
 * desc :
 * user : sixbing
 * time : 2019-03-27 11:30.
 * version : 1.0
 */

public class ChangeRequestUrlInterceptor implements Interceptor {
    private static String TAG = "ChangeRequestUrlInterceptor";
    private static String CHANGE_URL_FLAG = "CHANGE_URL_FLAG";
    public static final String CHANGE_URL_FLAG_HEADER = CHANGE_URL_FLAG + ": ";
    private static final String GLOBAL_DOMAIN_NAME = "me.jessyan.retrofiturlmanager.globalDomainName";
    private boolean isRun = true;//默认运行
    private final Map<String, HttpUrl> mDomainNameHub = new HashMap<>();
    /**
     * 如果在 Url 地址中加入此标识符, 将不会对此 Url 进行任何切换 BaseUrl 的操作
     */
    public static final String IDENTIFICATION_IGNORE = "#url_ignore";
    private Cache<String, String> mCache;

    public ChangeRequestUrlInterceptor() {
        this.mCache = new LruCache<>(100);
    }

    @Override
    public Response intercept(Chain chain) throws IOException {
        if (!isRun()) // 可以在 App 运行时, 随时通过 setRun(false) 来结束本框架的运行
            return chain.proceed(chain.request());
        return chain.proceed(processRequest(chain.request()));
    }

    /**
     * 对 {@link Request} 进行一些必要的加工, 执行切换 BaseUrl 的相关逻辑
     *
     * @param request {@link Request}
     * @return {@link Request}
     */
    private Request processRequest(Request request) {
        if (request == null) return null;

        Request.Builder newBuilder = request.newBuilder();

        String url = request.url().toString();
        //如果 Url 地址中包含 IDENTIFICATION_IGNORE 标识符, 框架将不会对此 Url 进行任何切换 BaseUrl 的操作
        if (url.contains(IDENTIFICATION_IGNORE)) {
            return pruneIdentification(newBuilder, url);
        }

        String changeBaseUrlKey = changeBaseUrlNameFromHeaders(request);

        HttpUrl newHttpUrl;
        // 如果有 header,获取 header 中 domainName 所映射的 url,若没有,则检查全局的 BaseUrl,未找到则为null
        if (!TextUtils.isEmpty(changeBaseUrlKey)) {
            newHttpUrl = fetchDomain(changeBaseUrlKey);
            newBuilder.removeHeader(CHANGE_URL_FLAG);
        } else {
            newHttpUrl = getGlobalDomain();
        }

        if (null != newHttpUrl) {
            HttpUrl newUrl = parseUrl(newHttpUrl, request.url());
            if (BuildConfig.DEBUG)
                LogUtil.e(TAG, "The new urlKey is " + changeBaseUrlKey + ", url is { " + newUrl.toString() + " }, old url is { " + request.url().toString() + " }");
            return newBuilder
                    .url(newUrl)
                    .build();
        }

        return newBuilder.build();

    }

    /**
     * 替换IP
     *
     * @param newHttpUrl http://192.168.1.158:8080/
     * @param url        http://www.baidu.com/video/main
     * @return http://192.168.1.158:8080/video/main
     */
    private HttpUrl parseUrl(HttpUrl newHttpUrl, HttpUrl url) {
        if (null == newHttpUrl) return url;
        HttpUrl.Builder builder = url.newBuilder();

        if (TextUtils.isEmpty(mCache.get(getKey(newHttpUrl, url)))) {
            for (int i = 0; i < url.pathSize(); i++) {
                //当删除了上一个 index, PathSegment 的 item 会自动前进一位, 所以 remove(0) 就好
                builder.removePathSegment(0);
            }
            List<String> newPathSegments = new ArrayList<>();
            newPathSegments.addAll(newHttpUrl.encodedPathSegments());
            newPathSegments.addAll(url.encodedPathSegments());

            for (String PathSegment : newPathSegments) {
                builder.addEncodedPathSegment(PathSegment);
            }
        } else {
            builder.encodedPath(mCache.get(getKey(newHttpUrl, url)));
        }

        HttpUrl httpUrl = builder
                .scheme(newHttpUrl.scheme())
                .host(newHttpUrl.host())
                .port(newHttpUrl.port())
                .build();

        if (TextUtils.isEmpty(mCache.get(getKey(newHttpUrl, url)))) {
            mCache.put(getKey(newHttpUrl, url), httpUrl.encodedPath());
        }
        return httpUrl;
    }

    private String getKey(HttpUrl newHttpUrl, HttpUrl url) {
        return newHttpUrl.encodedPath() + url.encodedPath();
    }

    /**
     * 存放 Domain(BaseUrl) 的映射关系
     *
     * @param domainName
     * @param domainUrl
     */
    public void putDomain(String domainName, String domainUrl) {
        checkNotNull(domainName, "domainName cannot be null");
        checkNotNull(domainUrl, "domainUrl cannot be null");
        synchronized (mDomainNameHub) {
            mDomainNameHub.put(domainName, checkUrl(domainUrl));
        }
    }

    /**
     * 检查url
     *
     * @param url url
     * @return HttpUrl
     */
    public static HttpUrl checkUrl(String url) {
        HttpUrl parseUrl = HttpUrl.parse(url);
        if (null == parseUrl) {
            throw new InvalidUrlException(url);
        } else {
            return parseUrl;
        }
    }

    /**
     * 取出对应 {@code domainName} 的 Url(BaseUrl)
     *
     * @param newBaseUrl
     * @return
     */
    public synchronized HttpUrl fetchDomain(String newBaseUrl) {
        checkNotNull(newBaseUrl, "domainName cannot be null");
        return mDomainNameHub.get(newBaseUrl);
    }

    /**
     * 移除某个 {@code domainName}
     *
     * @param domainName {@code domainName}
     */
    public void removeDomain(String domainName) {
        checkNotNull(domainName, "domainName cannot be null");
        synchronized (mDomainNameHub) {
            mDomainNameHub.remove(domainName);
        }
    }

    private static <T> T checkNotNull(T object, String message) {
        if (object == null) {
            throw new NullPointerException(message);
        }
        return object;
    }

    /**
     * 获取全局 BaseUrl
     */
    public synchronized HttpUrl getGlobalDomain() {
        return mDomainNameHub.get(GLOBAL_DOMAIN_NAME);
    }

    /**
     * 移除全局 BaseUrl
     */
    public void removeGlobalDomain() {
        synchronized (mDomainNameHub) {
            mDomainNameHub.remove(GLOBAL_DOMAIN_NAME);
        }
    }

    /**
     * 清理所有 Domain(BaseUrl)
     */
    public void clearAllDomain() {
        mDomainNameHub.clear();
    }

    /**
     * 存放 Domain(BaseUrl) 的容器中是否存在这个 {@code domainName}
     *
     * @param domainName {@code domainName}
     * @return {@code true} 为存在, {@code false} 为不存在
     */
    public synchronized boolean haveDomain(String domainName) {
        return mDomainNameHub.containsKey(domainName);
    }

    /**
     * 存放 Domain(BaseUrl) 的容器, 当前的大小
     *
     * @return 容量大小
     */
    public synchronized int domainSize() {
        return mDomainNameHub.size();
    }

    /**
     * 框架是否在运行
     *
     * @return {@code true} 为正在运行, {@code false} 为未运行
     */
    public boolean isRun() {
        return this.isRun;
    }

    /**
     * 控制框架是否运行, 在每个域名地址都已经确定, 不需要再动态更改时可设置为 {@code false}
     *
     * @param run {@code true} 为正在运行, {@code false} 为未运行
     */
    public void setRun(boolean run) {
        this.isRun = run;
    }

    /**
     * 将 {@code IDENTIFICATION_IGNORE} 从 Url 地址中修剪掉
     *
     * @param newBuilder {@link Request.Builder}
     * @param url        原始 Url 地址
     * @return 被修剪过 Url 地址的 {@link Request}
     */
    private Request pruneIdentification(Request.Builder newBuilder, String url) {
        String[] split = url.split(IDENTIFICATION_IGNORE);
        StringBuffer buffer = new StringBuffer();
        for (String s : split) {
            buffer.append(s);
        }
        return newBuilder
                .url(buffer.toString())
                .build();
    }

    /**
     * 从 {@link Request#header(String)} 中取出 CHANGE_URL_FLAG
     *
     * @param request {@link Request}
     * @return DomainName
     */
    private String changeBaseUrlNameFromHeaders(Request request) {
        List<String> headers = request.headers(CHANGE_URL_FLAG);
        if (headers == null || headers.size() == 0)
            return null;
        if (headers.size() > 1)
            throw new IllegalArgumentException("Only one Domain-Name in the headers");
        return request.header(CHANGE_URL_FLAG);
    }
}
